React ã®å®éšç㪠Taint API ã§ãã `experimental_taintObjectReference` ãš `experimental_taintUniqueValue` ãå©çšããŠããµãŒããŒããã¯ã©ã€ã¢ã³ããžã®æå³ããªãããŒã¿æŒæŽ©ãé²ããŸããã°ããŒãã«éçºè åãã®å æ¬çãªã¬ã€ãã
ããã³ãã£ã¢ã®åŒ·å: React ã®å®éšç㪠Taint API ãžã®éçºè ã®æ·±ãæ¢æ±
ãŠã§ãéçºã®é²åã¯ãå¢çç·ã®å€åã®ç©èªã§ããé·å¹ŽããµãŒããŒãšã¯ã©ã€ã¢ã³ãã®å¢çç·ã¯æç¢ºã§åããããããã®ã§ããã仿¥ãReact ãµãŒããŒã³ã³ããŒãã³ã (RSC) ã®ãããªã¢ãŒããã¯ãã£ã®åºçŸã«ããããã®å¢çç·ã¯ããééçãªèã«ãªãã€ã€ãããŸãããã®åŒ·åãªæ°ãããã©ãã€ã ã«ããããµãŒããŒåŽã®ããžãã¯ãšã¯ã©ã€ã¢ã³ãåŽã®ã€ã³ã¿ã©ã¯ãã£ããã£ãã·ãŒã ã¬ã¹ã«çµ±åã§ããé©ãã¹ãããã©ãŒãã³ã¹ãšéçºè ãšã¯ã¹ããªãšã³ã¹ã®åäžãæåŸ ã§ããŸãããããããã®æ°ããåã«ã¯ãæ°ããªçš®é¡ã®ã»ãã¥ãªãã£è²¬ä»»ã䌎ããŸããæ©å¯æ§ã®é«ããµãŒããŒåŽã®ããŒã¿ãæå³ããã«ã¯ã©ã€ã¢ã³ãåŽã®äžçã«æŒæŽ©ããã®ãé²ãããšã§ãã
ã¢ããªã±ãŒã·ã§ã³ãããŒã¿ããŒã¹ãããŠãŒã¶ãŒãªããžã§ã¯ããååŸããããšãæ³åããŠãã ããããã®ãªããžã§ã¯ãã«ã¯ããŠãŒã¶ãŒåã®ãããªå ¬éæ å ±ãå«ãŸããŠãããããããŸãããããã¹ã¯ãŒãããã·ã¥ãã»ãã·ã§ã³ããŒã¯ã³ããŸãã¯å人è奿 å ± (PII) ã®ãããªéåžžã«æ©å¯æ§ã®é«ãããŒã¿ãå«ãŸããŠããå¯èœæ§ããããŸããéçºã䜳å¢ã«å ¥ããšãéçºè ããã®ãªããžã§ã¯ãå šäœã Client Component ã«ããããã£ãšããŠæž¡ãã®ã¯éåžžã«ç°¡åã§å±éºã§ãããã®çµæãæ©å¯ããŒã¿ãã·ãªã¢ã«åããããããã¯ãŒã¯çµç±ã§éä¿¡ãããã¯ã©ã€ã¢ã³ãåŽã® JavaScript ãã€ããŒãã«çŽæ¥åã蟌ãŸãããã©ãŠã¶ã®éçºè ããŒã«ãæã£ãŠãã人ãªã誰ã§ãèŠãããšãã§ããŸããããã¯ä»®èª¬äžã®è åšã§ã¯ãããŸãããææ°ã®ãã¬ãŒã ã¯ãŒã¯ã察åŠããªããã°ãªããªãã埮åŠã§ãããªããé倧ãªè匱æ§ã§ãã
ããã§ãReact ã®æ°ããå®éšç㪠Taint API ã§ãã experimental_taintObjectReference ãš experimental_taintUniqueValue ãç»å ŽããŸãããããã®é¢æ°ã¯ããµãŒããŒãšã¯ã©ã€ã¢ã³ãã®å¢çç·ã«ãããã»ãã¥ãªãã£ã¬ãŒããšããŠæ©èœãããããã®çš®é¡ã®å¶çºçãªããŒã¿æŒæŽ©ãé²ãããã®å
ç¢ãªçµã¿èŸŒã¿ã¡ã«ããºã ãæäŸããŸãããã®èšäºã¯ãäžçäžã®éçºè
ãã»ãã¥ãªãã£ãšã³ãžãã¢ãããã³ã¢ãŒããã¯ãåãã®å
æ¬çãªã¬ã€ãã§ããåé¡ã詳现ã«èª¿æ»ãããããã®æ°ãã API ã®åäœãåæããå®çšçãªå®è£
æŠç¥ãæäŸããããå®å
šã§ã°ããŒãã«ã«æºæ ããã¢ããªã±ãŒã·ã§ã³ã®æ§ç¯ã«ããã圹å²ã«ã€ããŠèª¬æããŸãã
ããªãã: ãµãŒããŒã³ã³ããŒãã³ãã®ã»ãã¥ãªãã£ã®ã£ãããçè§£ãã
解決çãååã«çè§£ããããã«ã¯ããŸãåé¡ãæ·±ãçè§£ããå¿ èŠããããŸããReact ãµãŒããŒã³ã³ããŒãã³ãã®éæ³ã¯ããµãŒããŒäžã§å®è¡ããããŒã¿ããŒã¹ãå éš API ãªã©ã®ãµãŒããŒå°çšãªãœãŒã¹ã«ã¢ã¯ã»ã¹ããã¯ã©ã€ã¢ã³ãã«ã¹ããªãŒãã³ã°ããã UI ã®èšè¿°ãã¬ã³ããªã³ã°ããæ©èœã«ãããŸããããŒã¿ã¯ãServer Component ãã Client Component ã«ããããã£ãšããŠæž¡ãããšãã§ããŸãã
ãã®ããŒã¿ãããŒãè匱æ§ã®åå ã§ãããµãŒããŒç°å¢ããã¯ã©ã€ã¢ã³ãç°å¢ã«ããŒã¿ãæž¡ãããã»ã¹ã¯ãã·ãªã¢ã«å ãšåŒã°ããŸããReact ã¯ãããèªåçã«åŠçãããªããžã§ã¯ããšããããã£ããããã¯ãŒã¯çµç±ã§éä¿¡ããŠã¯ã©ã€ã¢ã³ãã§åæ§ç¯ã§ãã圢åŒã«å€æããŸãããã®ããã»ã¹ã¯å¹ççã§ãããç¡å·®å¥ã§ããã©ã®ããŒã¿ãæ©å¯ã§ãã©ã®ããŒã¿ãå®å šããç¥ããŸãããåã«äžãããããã®ãã·ãªã¢ã«åããã ãã§ãã
å€å žçãªã·ããªãª: æŒæŽ©ãããŠãŒã¶ãŒãªããžã§ã¯ã
App Router ã䜿çšãã Next.js ã®ãããªäžè¬çãªäŸã§èª¬æããŸãããããµãŒããŒåŽã®ããŒã¿ãã§ãã颿°ãèããŠã¿ãŸãããã
// app/data/users.js
import { db } from './database';
export async function getUser(userId) {
const user = await db.user.findUnique({ where: { id: userId } });
// The 'user' object might look like this:
// {
// id: 'user_123',
// name: 'Alice',
// email: 'alice@example.com', // Safe to display
// passwordHash: '...', // EXTREMELY SENSITIVE
// apiKey: 'secret_key_...', // EXTREMELY SENSITIVE
// twoFactorSecret: '...', // EXTREMELY SENSITIVE
// internalNotes: 'VIP customer' // Sensitive business data
// }
return user;
}
次ã«ãéçºè ããŠãŒã¶ãŒã®ãããã£ãŒã«ããŒãžã衚瀺ãã Server Component ãäœæããŸãã
// app/profile/[id]/page.js (Server Component)
import { getUser } from '@/app/data/users';
import UserProfileCard from '@/app/components/UserProfileCard'; // This is a Client Component
export default async function ProfilePage({ params }) {
const user = await getUser(params.id);
// The critical mistake is here:
return <UserProfileCard user={user} />;
}
ãããŠæåŸã«ããã®ããŒã¿ã䜿çšãã Client Component ã§ãã
// app/components/UserProfileCard.js
'use client';
export default function UserProfileCard({ user }) {
// This component only needs user.name and user.email
return (
<div>
<h1>{user.name}</h1>
<p>Email: {user.email}</p>
</div>
);
}
衚é¢äžããã®ã³ãŒãã¯ç¡å®³ã«èŠããå®å
šã«æ©èœããŸãããããã£ãŒã«ããŒãžã«ã¯ããŠãŒã¶ãŒã®ååãšã¡ãŒã«ã¢ãã¬ã¹ã衚瀺ãããŸããããããè£ã§ã¯ãã»ãã¥ãªãã£äžã®å€§æšäºãèµ·ãã£ãŠããŸãã`user` ãªããžã§ã¯ãå
šäœã UserProfileCard ã«ããããã£ãšããŠæž¡ããããããReact ã®ã·ãªã¢ã«åããã»ã¹ã«ã¯ããã¹ãŠã®ãã£ãŒã«ã ãå«ãŸããŠããŸãã`passwordHash`ã`apiKey`ã`twoFactorSecret`ãããã³ `internalNotes`ããã®æ©å¯ããŒã¿ã¯çŸåšãã¯ã©ã€ã¢ã³ãã®ãã©ãŠã¶ã¡ã¢ãªã«ååšããç°¡åã«æ€æ»ã§ãããããå€§èŠæš¡ãªã»ãã¥ãªãã£ããŒã«ãäœæãããŸãã
ããã¯ãŸãã« Taint API ã解決ããããã«èšèšãããŠããåé¡ã§ããReact ã«ããã®ç¹å®ã®ããŒã¿ã¯æ©å¯ã§ããã¯ã©ã€ã¢ã³ãã«éä¿¡ããããšãã詊ã¿ãæ€åºãããå Žåã¯ã忢ããŠãšã©ãŒãã¹ããŒããå¿ èŠããããŸãããšäŒããæ¹æ³ãæäŸããŸãã
Taint API ã®ç޹ä»: æ°ããé²åŸ¡ã¬ã€ã€ãŒ
ãæ±æãã®æŠå¿µã¯ãå€å žçãªã»ãã¥ãªãã£ååã§ããããã«ã¯ãä¿¡é Œã§ããªããœãŒã¹ããŸãã¯ãã®å Žåã¯ç¹æš©ãœãŒã¹ããã®ããŒã¿ã«ããŒã¯ãä»ããããšãå«ãŸããŸãããã®æ±æãããããŒã¿ãæ©å¯æ§ã®é«ãã³ã³ããã¹ã (ã¯ã©ã€ã¢ã³ãã«éä¿¡ãããªã©) ã§äœ¿çšããããšãããšããããã¯ãããŸããReact ã¯ããã®ã¢ã€ãã¢ã 2 ã€ã®ã·ã³ãã«ã§ãããªãã匷åãªé¢æ°ã§å®è£ ããŠããŸãã
-
<li><b>
experimental_taintObjectReference(message, object)</b>: ãã®é¢æ°ã¯ããªããžã§ã¯ãå
šäœãžã®åç
§ããæ¯ãããŸãã</li>
<li><b>experimental_taintUniqueValue(message, object, value)</b>: ãã®é¢æ°ã¯ããªããžã§ã¯ãã«é¢ä¿ãªããç¹å®ã®ãŠããŒã¯ãªå€ (ã·ãŒã¯ã¬ããããŒãªã©) ããæ¯ãããŸãã</li>
ããžã¿ã«ææããã¯ãšèããŠãã ããããµãŒããŒäžã®æ©å¯ããŒã¿ã«æ·»ä»ããŸãããã®ããŒã¿ãå®å šãªãµãŒããŒç°å¢ãé¢ããŠã¯ã©ã€ã¢ã³ããžã®å¢çç·ãè¶ããããšãããšãææããã¯ãççºããŸãããµã€ã¬ã³ãã«å€±æããã®ã§ã¯ãªãããµãŒããŒåŽãšã©ãŒãã¹ããŒãããªã¯ãšã¹ããéäžã§åæ¢ããããŒã¿æŒæŽ©ãé²ããŸããæäŸãããšã©ãŒã¡ãã»ãŒãžãå«ãŸããŠããããããããã°ãç°¡åã«ãªããŸãã
詳现ãªèª¿æ»: `experimental_taintObjectReference`
ããã¯ãã¯ã©ã€ã¢ã³ãã«å®å šã«éä¿¡ãããã¹ãã§ã¯ãªãè€éãªãªããžã§ã¯ããæ±æããããã®äž»åã§ãã
ç®çãšæ§æ
<p>ãã®äž»ãªç®æšã¯ããªããžã§ã¯ãã€ã³ã¹ã¿ã³ã¹ããµãŒããŒå°çšãšããŠããŒã¯ããããšã§ãããã®ç¹å®ã®ãªããžã§ã¯ãåç §ã Client Component ã«æž¡ãããšãããšãã·ãªã¢ã«åäžã«å€±æããŸãã</p> <p><strong>æ§æ:</strong> <code>experimental_taintObjectReference(message, object)</code></p> <ul> <li><code>message</code>: æŒæŽ©ã鲿¢ãããå Žåããšã©ãŒã¡ãã»ãŒãžã«å«ããæååãããã¯éçºè ã®ãããã°ã«äžå¯æ¬ ã§ãã</li> <li><code>object</code>: æ±æãããªããžã§ã¯ãåç §ã</li> </ul>å®éã®å®è£ æ¹æ³
<p>ãã®ä¿è·ãé©çšããŠã以åã®äŸããªãã¡ã¯ã¿ãªã³ã°ããŸããããããŒã¿ãæ±æããæé©ãªå Žæã¯ããŸãã«ãœãŒã¹ãã€ãŸãäœæãŸãã¯ãã§ãããããå Žæã§ãã</p>
// app/data/users.js (Now with tainting)
import { experimental_taintObjectReference } from 'react';
import { db } from './database';
export async function getUser(userId) {
const user = await db.user.findUnique({ where: { id: userId } });
if (user) {
// Taint the object as soon as we get it!
experimental_taintObjectReference(
'Security Violation: The full user object should not be passed to the client. ' +
'Instead, create a sanitized DTO (Data Transfer Object) with only the necessary fields.',
user
);
}
return user;
}
// app/profile/[id]/page.js (Server Component - NO CHANGE NEEDED HERE)
export default async function ProfilePage({ params }) {
const user = await getUser(params.id);
// This line will now cause a server-side error!
return <UserProfileCard user={user} />;
}
æ£ãããã¿ãŒã³: ãµãã¿ã€ãº
<p>ãšã©ãŒã¡ãã»ãŒãžã¯ãæ£ãã解決çãã€ãŸãã¯ã©ã€ã¢ã³ãçšã«ãµãã¿ã€ãºããããªããžã§ã¯ããäœæããããã«æç€ºããŸãã</p>
// app/profile/[id]/page.js (Server Component - CORRECTED)
import { getUser } from '@/app/data/users';
import UserProfileCard from '@/app/components/UserProfileCard';
export default async function ProfilePage({ params }) {
const user = await getUser(params.id);
// If user not found, handle it (e.g., notFound() in Next.js)
if (!user) { ... }
// Create a new, clean object for the client
const userForClient = {
name: user.name,
email: user.email
};
// This is safe because userForClient is a brand new object
// and its reference is not tainted.
return <UserProfileCard user={userForClient} />;
}
詳现ãªèª¿æ»: `experimental_taintUniqueValue`
<p>`taintObjectReference` ãã³ã³ããã«é¢ãããã®ã§ããã®ã«å¯Ÿãã`taintUniqueValue` ã¯ã³ã³ãã³ãã«é¢ãããã®ã§ããç¹å®ã®ããªããã£ãå€ (æååãæ°å€ãªã©) ãæ±æãããããã©ã®ããã«ããã±ãŒãžåãããŠããŠããã¯ã©ã€ã¢ã³ãã«éä¿¡ãããªãããã«ããŸãã</p>ç®çãšæ§æ
<p>ããã¯ãéåžžã«æ©å¯æ§ãé«ããæŸå°æ§ç©è³ªãšèŠãªãããã¹ãå€ãã€ãŸã API ããŒãããŒã¯ã³ãã·ãŒã¯ã¬ããçšã§ãããã®å€ãã¯ã©ã€ã¢ã³ãã«éä¿¡ãããããŒã¿ã«ã©ããã«è¡šç€ºãããå Žåãããã»ã¹ã忢ããå¿ èŠããããŸãã</p> <p><strong>æ§æ:</strong> <code>experimental_taintUniqueValue(message, object, value)</code></p> <ul> <li><code>message</code>: 説æçãªãšã©ãŒã¡ãã»ãŒãžã</li> <li><code>object</code>: å€ãä¿æãããªããžã§ã¯ããããã¯ãReact ãæ±æãå€ã«é¢é£ä»ããããã«äœ¿çšãããŸãã</li> <li><code>value</code>: æ±æããå®éã®æ©å¯å€ã</li> </ul>å®éã®å®è£ æ¹æ³
<p>ãã®é¢æ°ã¯ãæ±æãå€èªäœã«åŸããããéåžžã«åŒ·åã§ãããµãŒããŒäžã®ç°å¢å€æ°ã®ããŒããèããŠã¿ãŸãããã</p>
// app/config.js (Server-only module)
import { experimental_taintUniqueValue } from 'react';
export const serverConfig = {
DATABASE_URL: process.env.DATABASE_URL,
API_SECRET_KEY: process.env.API_SECRET_KEY,
PUBLIC_API_ENDPOINT: 'https://api.example.com/public'
};
// Taint the secret key immediately after loading it
if (serverConfig.API_SECRET_KEY) {
experimental_taintUniqueValue(
'CRITICAL: API_SECRET_KEY must never be exposed to the client.',
serverConfig, // The object holding the value
serverConfig.API_SECRET_KEY // The value itself
);
}
// app/some-page/page.js (Server Component)
import { serverConfig } from '@/app/config';
import SomeClientComponent from '@/app/components/SomeClientComponent';
export default function SomePage() {
// Developer creates an object for the client
const clientProps = {
endpoint: serverConfig.PUBLIC_API_ENDPOINT,
// The mistake:
apiKey: serverConfig.API_SECRET_KEY
};
// This will throw an error!
return <SomeClientComponent config={clientProps} />;
}
å®çšçãªå®è£ æŠç¥: ã°ããŒãã«ã¢ãããŒã
<p>ãããã® API ã广çã«äœ¿çšããã«ã¯ãæ£çºçã«ã§ã¯ãªããäœç³»çã«é©çšããå¿ èŠããããŸããçµ±åããæé©ãªå Žæã¯ãæ©å¯ããŒã¿ãã¢ããªã±ãŒã·ã§ã³ã«å ¥åãããå¢çç·ã§ãã</p>1. ããŒã¿ã¢ã¯ã»ã¹å±€
<p>ããã¯æãéèŠãªå Žæã§ããããŒã¿ããŒã¹ã¯ã©ã€ã¢ã³ã (PrismaãDrizzle ãªã©) ã䜿çšããŠããããå éš API ãããã§ããããŠãããã«ããããããçµæãæ±æãã颿°ã§ã©ããããŸãã</p>
// app/lib/security.js
import { experimental_taintObjectReference } from 'react';
const SENSITIVE_OBJECT_MESSAGE =
'Security Violation: This object contains sensitive server-only data and cannot be passed to a client component. ' +
'Please create a sanitized DTO for client use.';
export function taintSensitiveObject(obj) {
if (process.env.NODE_ENV === 'development' && obj) {
experimental_taintObjectReference(SENSITIVE_OBJECT_MESSAGE, obj);
}
return obj;
}
// Now use it in your data fetchers
import { db } from './database';
import { taintSensitiveObject } from './security';
export async function getFullUser(userId) {
const user = await db.user.findUnique({ where: { id: userId } });
return taintSensitiveObject(user);
}
2. ç°å¢å€æ°ãšæ§æã®ããŒã
<p>ã¢ããªã±ãŒã·ã§ã³ã®èµ·åçŽåŸã«ããã¹ãŠã®ã·ãŒã¯ã¬ããå€ãæ±æããŸããæ§æãåŠçããããã®å°çšã¢ãžã¥ãŒã«ãäœæããŸãã</p>
// app/config/server-env.js
import { experimental_taintUniqueValue } from 'react';
const env = {
STRIPE_SECRET_KEY: process.env.STRIPE_SECRET_KEY,
SENDGRID_API_KEY: process.env.SENDGRID_API_KEY,
// ... other secrets
};
function taintEnvSecrets() {
for (const key in env) {
const value = env[key];
if (value) {
experimental_taintUniqueValue(
`Security Alert: Environment variable ${key} cannot be sent to the client.`,
env,
value
);
}
}
}
taintEnvSecrets();
export default env;
3. èªèšŒããã³ã»ãã·ã§ã³ãªããžã§ã¯ã
<p>ãŠãŒã¶ã»ãã·ã§ã³ãªããžã§ã¯ãã¯ãå€ãã®å Žåã¢ã¯ã»ã¹ããŒã¯ã³ããªãã¬ãã·ã¥ããŒã¯ã³ããŸãã¯ãã®ä»ã®æ©å¯ã¡ã¿ããŒã¿ãå«ãŸããŠãããæ±æã®äž»èŠãªåè£ã§ãã</p>
// app/lib/auth.js
import { getSession } from 'next-auth/react'; // Example library
import { taintSensitiveObject } from './security';
export async function getCurrentUserSession() {
const session = await getSession(); // This might contain sensitive tokens
return taintSensitiveObject(session);
}